#include "c3dsimporter.h"

/**
 * import3DS - imports 3DS model
 * @param model
 * @param fileName
 */
void C3DSImporter::import3DS(CModel &model, string fileName) {

  // Open file, read root chunk
  fstream fh(fileName.c_str(), ios::in | ios::binary);
  
  // If file is not open, throw exception
  if (!fh.is_open()) {
    throw CFileNotExistException(getClassName(), "import3DS");
  }
  
  // Read root chunk
  readRootChunk(fh, model);
  
  // Convert percentage values
  convertPercentageValues(model);
  
  // Close file
  fh.close();
}
//---------------------------------------------------------------------------

/**
 * readRootChunk - reads root chunk
 * @param fh
 * @param model
 */
void C3DSImporter::readRootChunk(fstream &fh, CModel &model) {
 
  // Read chunk from data stream
  CInChunk chunk;
  chunk.loadFromStream(fh);
    
  // If chunk is not *.3DS root, throw exception
  if (chunk.getId() != C3DSFileIdentifications::ROOT) {
    throw CBadFileException(getClassName(), "readRootChunk");
  }
  
  // Read sub root chunk
  readRootSubChunk(chunk, model);
}
//---------------------------------------------------------------------------

/**
 * readRootSubChunk - reads sub root chunks
 * @param chunk
 * @param model
 */
void C3DSImporter::readRootSubChunk(CInChunk &chunk, CModel &model) {
  
  // Read sub root chunks
  while (chunk.getBytesRead() < chunk.getLength()) {    
    CInChunk tmpChunk = chunk.readChunk();    
    switch (tmpChunk.getId()) {       
      case C3DSFileIdentifications::MATERIAL :
        readMaterial(tmpChunk, model);
        break;
      case C3DSFileIdentifications::MESH :
        readMesh(tmpChunk, model);
        break;
      case C3DSFileIdentifications::ROOT_SUB :
        readRootSubChunk(tmpChunk, model);
        break;
    }
  }
}
//---------------------------------------------------------------------------

/**
 * readMesh - reads model mesh
 * @param chunk
 * @param model
 */
void C3DSImporter::readMesh(CInChunk &chunk, CModel &model) {
  
  // New mesh
  CMesh* mesh = model.addNewMesh();
  
  // Read sub chunks
  while (chunk.getBytesRead() < chunk.getLength()) {
    CInChunk tmpChunk = chunk.readChunk();

    switch (tmpChunk.getId()) {
      case C3DSFileIdentifications::MESH_VERTS :
        readMeshVerts(tmpChunk, mesh);
        break;
      case C3DSFileIdentifications::MESH_COORDS :
        readMeshCoords(tmpChunk, mesh);
        break;
      case C3DSFileIdentifications::MESH_FACES :
        readMeshFaces(tmpChunk, mesh);
        break;
      case C3DSFileIdentifications::MESH_MATERIAL :
        readMeshMaterial(tmpChunk, model, mesh);
        break;
      case C3DSFileIdentifications::MESH_MESH :
        readMesh(tmpChunk, model);
        break;
    }
  }
}
//---------------------------------------------------------------------------

/**
 * readMeshVerts - reads mesh vertices
 * @param chunk
 * @param mesh
 */
void C3DSImporter::readMeshVerts(CInChunk &chunk, CMesh* mesh) {

  // Create and read mesh vertices
  CVector3D<float>* verts = mesh->createVerts(chunk.readUShort());
  unsigned int ms = sizeof(float) * mesh->getNumOfVerts() *
                    CVector<float>::VECTOR_SPACE_3D;
  chunk.readBuffer((char*) verts, ms);

  // Go through vertices and swap y and z axis coords
  for (int i = 0; i < mesh->getNumOfVerts(); i++) {
    float tmp = mesh->getVertsPtr()[i].getY();
    mesh->getVertsPtr()[i].setY(mesh->getVertsPtr()[i].getZ());
    mesh->getVertsPtr()[i].setZ(-tmp);
  }
}
//---------------------------------------------------------------------------

/**
 * readMeshCoords - reads mesh texture coords
 * @param chunk
 * @param mesh
 */
void C3DSImporter::readMeshCoords(CInChunk &chunk, CMesh* mesh) {

  // Read and create texture vertices of mesh
  CVector2D<float>* coords = mesh->createCoords(chunk.readUShort());
  unsigned int ms = sizeof(float) * mesh->getNumOfCoords() *
                    CVector<float>::VECTOR_SPACE_2D;
  chunk.readBuffer((char*) coords, ms);
}
//---------------------------------------------------------------------------

/**
 * readMeshFaces - reads mesh faces
 * @param chunk
 * @param mesh
 */
void C3DSImporter::readMeshFaces(CInChunk &chunk, CMesh* mesh) {

  // Create mesh faces
  TTriangle* faces = mesh->createFaces(chunk.readUShort());
 
  // Read indices of face vertices
  for (int i = 0; i < mesh->getNumOfFaces(); i++) {
    for (int j = 0; j < 4; j++) {
      unsigned short index = chunk.readUShort();
      if (j < 3) faces[i].vertIds[j] = index;
    }
  }
}
//---------------------------------------------------------------------------

/**
 * readMeshMaterial - reads mesh material
 * @param chunk
 * @param mesh
 */
void C3DSImporter::readMeshMaterial(CInChunk &chunk, CModel &model,
                                    CMesh* mesh) {

  // Reads name of mesh material
  string strMaterial = chunk.readPChar();

  // Default setting, mesh does not have material
  mesh->setMaterialId(-1);

  // Go through all materials of model
  for (int i = 0; i < model.getNumOfMaterials(); i++) {

    // Pointer on currently selected material
    CMaterial* material = model.getMaterialPtr(i);

    // If name of read material matches name of mesh material, save
    // material id and return
    if (strcmp(strMaterial.c_str(), material->getStrName().c_str()) == 0) {
      mesh->setMaterialId(i);
      break;
    }
  }
}
//---------------------------------------------------------------------------

/**
 * readMaterial - reads model material
 * @param chunk
 * @param model
 */
void C3DSImporter::readMaterial(CInChunk &chunk, CModel &model) {

  // New material
  CMaterial* mat = model.addNewMaterial();

  // Read sub chunks
  while (chunk.getBytesRead() < chunk.getLength()) {
    CInChunk tmpChunk = chunk.readChunk();

    switch (tmpChunk.getId()) {
      case C3DSFileIdentifications::MATERIAL_NAME :
        mat->setStrName(read255CharChunk(tmpChunk));
        break;
      case C3DSFileIdentifications::MATERIAL_AMBIENT :
        mat->setAmbient(tmpChunk.readCColor());
        break;
      case C3DSFileIdentifications::MATERIAL_DIFFUSE :
        mat->setDiffuse(tmpChunk.readCColor());
        break;
      case C3DSFileIdentifications::MATERIAL_SPECULAR :
        mat->setSpecular(tmpChunk.readCColor());
        break;
      case C3DSFileIdentifications::MATERIAL_SHININESS :
        mat->setShininess(tmpChunk.readShort());
        break;
      case C3DSFileIdentifications::MATERIAL_TRANSPARENT :
        mat->setTransparency(tmpChunk.readShort());
        break;
      case C3DSFileIdentifications::MATERIAL_MAPFILE :
        mat->setStrTextureFileName(read255CharChunk(tmpChunk));
        break;
      case C3DSFileIdentifications::MATERIAL_MATERIAL :
        readMaterial(tmpChunk, model);
        break;
    }
  }
}
//---------------------------------------------------------------------------

/**
 * read255CharChunk - reads sequence in char[255] and saves it to string
 * @param chunk
 */
string C3DSImporter::read255CharChunk(CInChunk &chunk) {
  char tmp[255] = {0};
  chunk.readBuffer(tmp, chunk.getLength() - chunk.getBytesRead());
  return string(tmp);
}
//---------------------------------------------------------------------------

/**
 * convertPercentageValues - converts from percentage values
 * @param model
 */
void C3DSImporter::convertPercentageValues(CModel &model) {

  // Go through all model materials
  for (int i = 0; i < model.getNumOfMaterials(); i++) {

    // Converts transparency percentage into alpha
    short transp = model.getMaterialPtr(i)->getTransparency();
    model.getMaterialPtr(i)->setTransparency(((100 - transp) * 255) / 100);

    // Converts shininess
    short shine = model.getMaterialPtr(i)->getShininess();
    model.getMaterialPtr(i)->setShininess((shine * 128) / 100);
  }
}
//---------------------------------------------------------------------------
